#region Copyright Vanderlande Industries B.V. 2009-2011
//
// Copyright (c) 2009-2011 Vanderlande Industries.
// All rights reserved.
//
// @file:     Subscription.cs
// @version:  %version: 28 %
// %date_created: donderdag 14 juni 2012 10:54:50 %
//
// The copyright to the computer program(s) herein is the property of
// Vanderlande Industries. The program(s) may be used and/or copied
// only with the written permission of the owner or in accordance with
// the terms and conditions stipulated in the contract under which the
// program(s) have been supplied.
//
#endregion

using System;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
//using VI.Bfw.Logging;
using VI.Fsc.OperatorGui.Models.Application;
using VI.Fsc.OperatorGui.WebReferences.Opc;
//using VI.Bfw.Components;

namespace VI.Fsc.OperatorGui.Models.Subscription
{
    public delegate void SubscribedItemsChangedEventHandler(SubscribePolledRefreshReplyItemList[] list);
    public delegate void SubscriptionExceptionEventHandler(Exception e);

    /// <summary>
    /// Encapsulates subscription of items from an OPC server.
    /// </summary>
    public class CSubscription
    {
        public event SubscribedItemsChangedEventHandler SubscribedItemsChanged;
        public event SubscriptionExceptionEventHandler ExceptionEvent;

        private OpcXmlDaWebService server;
        private string id;
        volatile private bool isRunning;
        
        #region SubscribeRequest related attributes
        /// <summary>
        /// Options of the request. Used by both the subscribe call and the poll call.
        /// </summary>
        private RequestOptions requestOptions;

        /// <summary>
        /// If set to true the server will return item values which are readily available 
        /// for inclusion in the response. Depending on when the PollRefresh is requested,
        /// these items may or may not be updated in the first PolledRefresh call.
        /// If false the server must not deliver any item values in the SubscribeResponse.
        /// </summary>
        private bool returnValuesOnReply;

        /// <summary>
        /// The requested rate in milliseconds that the server should reevaluate the
        /// existence of the client.
        /// </summary>
        private int pingRate;

        /// <summary>
        /// The container of the individual items.
        /// </summary>
        private SubscribeRequestItemList itemList;
        #endregion

        #region PollRefreshRequest related attributes
        /// <summary>
        /// Supplied by the server in the response, it is used by the server 
        /// to identify the subscription to be polled.
        /// </summary>
        private string serverSubHandle;

        /// <summary>
        /// Instructs the server to hold off returning from the refresh service call 
        /// until the absolute time of the server is equal or greater than this 
        /// value. We set it to any value, since we can not warranty the client
        /// time is the same as the server time (i.e. not really used).
        /// </summary>
        private DateTime holdTime;

        /// <summary>
        /// Flag indicating if the hold time is specified or not.
        /// </summary>
        private bool isHoldTimeSpecified;

        /// <summary>
        /// Instructs the server to wait the specified number of milliseconds after 
        /// holdTime before running if there are no changes to report. 
        /// See <see cref="holdTime"/> to see why this wait time is not really used.
        /// </summary>
        private int waitTime;

        /// <summary>
        /// Boolean flag indicating if all items need to be returned upon expiration of
        /// the subscription, or if only changes need to be returned.
        /// </summary>
        private bool returnAllItems;
        #endregion

        /// <summary>
        /// The list of ItemLists returned by the server. One ItemList for each subHandle, 
        /// which in this case turns out to be just 1 handle.
        /// </summary>
        private SubscribePolledRefreshReplyItemList[] replyItemList;

        public CSubscription(OpcXmlDaWebService opcServer, short requestedPingRate, 
            SubscribeRequestItemList list)
        {
            server = opcServer;
            id = Guid.NewGuid().ToString();
            isRunning = false;

            // SubscribeRequest attributes.
            requestOptions = GetDefaultRequestOptions();
            returnValuesOnReply = false; // We don't need the values to be returned on subscribeResponse.
            pingRate = requestedPingRate;
            itemList = list;
            
            // PollRefresh attributes.
            serverSubHandle = "";
            holdTime = DateTime.MinValue; // we can not specify a holdTime since the server may have a different time.
            isHoldTimeSpecified = false;
            waitTime = 1000; // any value is ok. See comments in the declaration.
            returnAllItems = false;
        }

        public string Id
        {
            get
            {
                return this.id;
            }
        }

        public void NotifySubscribedItemsChanges()
        {
            if (SubscribedItemsChanged != null)
            {
                SubscribedItemsChanged(replyItemList);
            }
        }

        /// <summary>
        /// Cancels the subscription, when the server is online.
        /// </summary>
        /// <param name="notifyWhenStopped">true: An event notifying about this change of the subscription is raised.
        /// </param>
        public void Stop(bool notifyWhenStopped)
        {
            //Logger.Entering(MethodBase.GetCurrentMethod(), "notifyWhenStopped = " + notifyWhenStopped);

            if (COPCServerConnectionService.Instance.IsOnline)
            {
                // When the subscriptionHandler is trying to cancel the subscription and we lose connection,
                // we get a crash. This occurs very rarely, but this is the reason the try-catch is needed.
                try
                {
                    string clientRequestHandle = null;
                    server.SubscriptionCancel(serverSubHandle, ref clientRequestHandle);
                }
                catch (System.Net.WebException webEx)
                {
                    //Logger.Warn(MethodBase.GetCurrentMethod(), "A webException occurred while trying to stop the subscription", webEx);
                }
            }
            
            isRunning = false;
            replyItemList = null;
            if (notifyWhenStopped)
            {
                NotifySubscribedItemsChanges();
            }

            //Logger.Exiting(MethodBase.GetCurrentMethod());
        }

        /// <summary>
        /// Starts the subscription.
        /// </summary>
        public void Start()
        {
            //Logger.Entering(MethodBase.GetCurrentMethod());

            SubscribeReplyItemList replyList = null;
            OPCError[] errors = null;

            try
            {
                // Subscribe the items.
                ReplyBase reply = 
                    server.Subscribe(
                        requestOptions, itemList,
                        returnValuesOnReply, pingRate,
                        out replyList, out errors, out serverSubHandle);

                // Handle the errors, if any.
                if (errors.Length > 0)
                {
                    foreach (OPCError error in errors)
                    {
                        HandleOpcError(error);
                    }
                }
                else
                {
                    // Set the subscription flag as running.
                    isRunning = true;
                    // Start pinging the server.
                    PingServer();
                }
            }
            catch (System.Net.WebException webEx)
            {
                FireException(webEx);
            }
            catch (ThreadAbortException e)
            {
                //Logger.Info(MethodBase.GetCurrentMethod(), e);
            }
            catch (Exception ex) // As in the RCF-Main program we exit if an exception arrives unhandled here.
            {
                //Logger.Fatal(MethodBase.GetCurrentMethod(), "Uncaught exception occurred.", ex);
                MessageBox.Show("Uncaught exception occurred: " + ex.Message + "\nSee log for details.", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                Environment.Exit(1);
            }

            //Logger.Exiting(MethodBase.GetCurrentMethod());
        }

        private void PingServer()
        {
            //Logger.Entering(MethodBase.GetCurrentMethod());

            try
            {
                while (this.isRunning)
                {
                    string[] serverSubHandles = new string[] { serverSubHandle };
                    string[] invalidServerSubHandles = null;
                    OPCError[] errors = null;
                    bool dataBufferOverflow = false;
                    
                    // Clean the reply item list.
                    replyItemList = null;
                    
                    // Call the polledRefresh.
                    server.SubscriptionPolledRefresh(
                        requestOptions, serverSubHandles, holdTime,
                        isHoldTimeSpecified, waitTime, returnAllItems,
                        out invalidServerSubHandles, out replyItemList,
                        out errors, out dataBufferOverflow);

                    // Handle the errors, if any.
                    if (errors.Length > 0)
                    {
                        foreach (OPCError error in errors)
                        {
                            HandleOpcError(error);
                        }
                    }
                    else
                    {
                        // Check if the subscription was killed by the server, i.e. if
                        // we have an invalid server sub handler. If so, we need restart the 
                        // subscription.
                        if (invalidServerSubHandles.Length > 0)
                        {
                            Restart();
                        }
                        else
                        {
                            // Notify about the changes in the subscribed items, since
                            // no errors were found and the server did respond correctly.
                            // Note: we notify only if changes were received.
                            if (replyItemList.Length > 0)
                            {
                                NotifySubscribedItemsChanges();
                            }

                            // Wait before executing the next call.
                            System.Threading.Thread.Sleep(pingRate);
                        }
                    }
                }
            }
            catch (System.Net.WebException webEx)
            {
                // If we have an exception:
                // first check if the requested failed because of a time out in the subscription,
                // but not of a time out in the connexion. To check it, we ask the connexion server
                // if there is connexion to the server, and if the answer is yes, then we 
                // restart the subscription; if the answer is no, we set the online status to false, 
                // and notify by firing an event.
                if (COPCServerConnectionService.Instance.IsServerRunning())
                {
                    Restart();
                }
                else
                {
                    FireException(webEx);
                }
            }
            catch (Exception ex)
            {
                //Logger.Error(MethodBase.GetCurrentMethod(), ex.Message);
            }

            //Logger.Exiting(MethodBase.GetCurrentMethod());
        }

        private void FireException(Exception exception)
        {
            //Logger.Info(MethodBase.GetCurrentMethod(), "Raise an event for an exception. " + exception);
            COPCServerConnectionService.Instance.IsOnline = false;

            if (ExceptionEvent != null)
            {
                ExceptionEvent(exception);
            }
        }

        private void Restart()
        {
            isRunning = false;
            Start();
        }

        private void HandleOpcError(OPCError error)
        {
            isRunning = false;

            //Logger.Error(MethodBase.GetCurrentMethod(), error.ID.Name + ": " + error.Text);

            //Logger.Info(MethodBase.GetCurrentMethod(), "Error VIDialog opened");

            // Also show a message instead of only logging the opc error.
           // VIDialog.ShowErrorDialog("FSC", "OpcErrorTitle", "OpcErrorMessage");

            //Logger.Info(MethodBase.GetCurrentMethod(), "Error VIDialog closed");
        }

        private RequestOptions GetDefaultRequestOptions()
        {
            RequestOptions requestOptions = new RequestOptions();
            
            requestOptions.ReturnErrorText = true;
            requestOptions.ReturnItemTime = true;
            requestOptions.ReturnItemName = true;
            requestOptions.ReturnItemPath = true;
            requestOptions.LocaleID = "en-US";
            requestOptions.ReturnDiagnosticInfo = false;
            requestOptions.ClientRequestHandle = this.id;

            return requestOptions;
        }
    }
}
